package eu.liquify.ui {
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.system.ApplicationDomain;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getQualifiedSuperclassName;
	import no.doomsday.console.ConsoleUtil;
	public class Layout {
		private static var inst:Layout;
		private static function get instance():Layout {
			if (!inst) inst = new Layout();
			return inst;
		}
		
		
		private var classes:Vector.<Class> = new <Class>[Button, Spinner, Box, VBox, HBox, Panel, ScrollablePanel, ListBox, Label, MultiTabBox, TitleLabel, ProgressBar, GridIndicator];
		
		
		public function Layout() {}
		private function getClassName(c:Class):String {
			var qcn:String = getQualifiedClassName(c);
			return qcn.substring(qcn.lastIndexOf("::")+2);
		}
		/*
		private function inherits(parent:Class, child:Class):Boolean {
			var type:Class = child;
			while (true) {
				if (parent == type) return true;
				var sc:String = getQualifiedSuperclassName(type);
				if (!sc) break;
				type = ApplicationDomain.currentDomain.getDefinition(sc) as Class;
			}
			return false;
		}
		*/
		private function getClass(name:String, parent:Class = null):Class {
			for each (var c:Class in classes) {
				if (name.toLowerCase() == getClassName(c).toLowerCase()) return c;
			}
			
			if (parent) {
				var ca:Object = parent.childAliases;
				if (ca && name in ca) {
					return ca[name];
				}
			}
			
			if (!ApplicationDomain.currentDomain.hasDefinition(name)) return null;
			var def:Object = ApplicationDomain.currentDomain.getDefinition(name);
			return def as Class;
		}
		private function resolvePath(target:Object, path:String):PropertyReference {
			var split:Vector.<String> = Vector.<String>(path.split("."));
			var prop:String = split.pop();
			while (split.length > 0) {
				var current:String = split.shift();
				if (current in target) {
					target = target[current];
				} else {
					throw new Error("Property "+path+" is invalid: "+current+" not found in "+target);
				}
			}
			return new PropertyReference(target, prop);
		}
		
		public static function parseAll(target:DisplayObjectContainer, xml:XML, bindingsOutput:Vector.<Binding> = null):Vector.<DisplayObject> { return instance.parseAll(target, xml, bindingsOutput); }
		public function parseAll(target:DisplayObjectContainer, xml:XML, bindingsOutput:Vector.<Binding> = null):Vector.<DisplayObject> {
			var items:Vector.<DisplayObject> = new Vector.<DisplayObject>();
			for each (var child:XML in xml.children()) {
				items.push(parse(target, <comps>{child}</comps>, bindingsOutput));
			}
			return items;
		}
		
		public static function parse(target:DisplayObjectContainer, xml:XML, bindingsOutput:Vector.<Binding> = null):DisplayObject { return instance.parse(target, xml, bindingsOutput); }
		public function parse(target:DisplayObjectContainer, xml:XML, bindingsOutput:Vector.<Binding> = null):DisplayObject {
			var items:Vector.<DisplayObject> = parseItems(target, xml, bindingsOutput);

			if (items.length == 0) {
				return null;
			} else if (items.length == 1) {
				return items[0];
			}
			/*
			var container:DisplayObjectContainer = new Grid();
			for each (var childItem:DisplayObject in items) {
				container.addChild(childItem);
			}
			*/
			return items[0];
		}
		private function parseItems(target:DisplayObjectContainer, xml:XML, bindingsOutput:Vector.<Binding> = null, parent:Class = null):Vector.<DisplayObject> {
			var items:Vector.<DisplayObject> = new Vector.<DisplayObject>();
			var item:DisplayObject;
			for each (var child:XML in xml.children()) {
				var c:Class = getClass(child.name(), parent);
				if (!c) throw new ReferenceError("Component or class "+child.name()+" not found.");
				item = new c();
				for each (var attr:XML in child.attributes()) {
					var prop:String = attr.name();
					
					var ref:PropertyReference;
					
					if (prop == "id") {
						var id:String = String(attr);
						ref = resolvePath(target, id);
						if (!(ref.property in ref.target)) throw new Error("Property "+ref.property+" not found in "+ref.target+".");
						ref.setProperty(item); 
						continue;
					}
					
					if (!(prop in item)) throw new Error("Property "+prop+" not found in "+getClassName(c)+".");
					var propType:String = "";
					
					try {
						propType = getQualifiedClassName(item[prop]);
					} catch (error:ReferenceError) { }
					
					switch (propType) {
						case "Function":
							var targetPaths:String = String(attr);
							var paths:Vector.<String> = Vector.<String>(targetPaths.split(";"));
							
							/** A list of all the bindings and function callbacks */
							var tl:TargetList = paths.length > 1 ? new TargetList() : null;
							for each (var path:String in paths) {
								ref = resolvePath(target, path);
								
								propType = "";
								try {
									propType = getQualifiedClassName(ref.getProperty());
								} catch (error:ReferenceError) { }
								
								switch (propType) {
									case "builtin.as$0::MethodClosure":
									case "Function":
										if (tl) {
											tl.add(ref.getProperty());
										} else {
											item[prop] = ref.getProperty();
										}
										break;
									default:
										if (!(item is IHasBindValue)) throw new Error("Binding error: Item "+item+" does not implement the IHasValue interface.");
										var b:Binding = new Binding(item as IHasBindValue, ref.target, ref.property);
										if (tl) {
											tl.add(b);
										} else {
											item[prop] = b.apply;
										}
										if (bindingsOutput) bindingsOutput.push(b);
								}
							}
							if (tl) {
								item[prop] = tl.execute;
							}
							
							break;
						case "Boolean":
							item[prop] = String(attr) == "true";
							break;
						default:
							item[prop] = String(attr);
					}
				}
				if (child.children().length() > 0) {
					if (!(item is DisplayObjectContainer)) throw new Error("Component or class "+child.name()+" has defined children, but is not a DisplayObjectContainer."); 
					var childItems:Vector.<DisplayObject> = parseItems(target, child, bindingsOutput, c);
					var container:DisplayObjectContainer = item as DisplayObjectContainer;
					for each (var childItem:DisplayObject in childItems) {
						container.addChild(childItem);
					}
				}
				items.push(item);
			}
			return items;
		}
	}

}